package C_java类库新特性.Stream;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Created by zc on 2017/3/10.
 * <p>
 *      最新添加的Stream API（java.util.stream）把真正的函数式编程风格引入到Java中。
 * 这是目前为止对Java类库最好的补充，因为Stream API可以极大提供Java程序员的生产力，
 * 让程序员写出高效率、干净、简洁的代码。
 * <p>
 * Stream API极大简化了集合框架的处理（但它的处理的范围不仅仅限于集合框架的处理，这点后面我们会看到）。
 */
public class Streams {
    private enum Status {
        OPEN, CLOSED
    }

    private static final class Task {
        private final Status status;
        private final Integer points;

        public Task(final Status status, final Integer points) {
            this.status = status;
            this.points = points;
        }

        public Status getStatus() {
            return status;
        }

        public Integer getPoints() {
            return points;
        }

        @Override
        public String toString() {
            return String.format("[%s, %d]", status, points);
        }
    }

    /**
     * Task类有一个分数的概念（或者说是伪复杂度），
     * 其次是还有一个值可以为OPEN或CLOSED的状态.
     * 让我们引入一个Task的小集合作为演示例子：
     */
    static final Collection<Task> tasks = Arrays.asList(
        new Task(Status.OPEN, 5),
        new Task(Status.OPEN, 10),
        new Task(Status.CLOSED, 5)
    );

    public static void main(String[] args) {
        /**
         * 我们下面要讨论的第一个问题是所有状态为OPEN的任务一共有多少分数？
         *      在Java 8以前，一般的解决方式用foreach循环，
         *      但是在Java 8里面我们可以使用stream：
         *          一串支持连续、并行聚集操作的元素。
         */
        final long totalPointsOfTasks = tasks
                .stream()
                .filter(task -> task.getStatus() == Status.OPEN)
                .mapToInt(Task::getPoints) // 或 map( task -> getPoints() )
                .sum();
        System.out.println("活跃的节点总数为：" + totalPointsOfTasks); // 16

        /**
         * 这里有几个注意事项。第一，tasks集合被转换化为其相应的stream表示。
         * 然后，filter操作过滤掉状态为CLOSED的task。
         * 下一步，mapToInt操作通过Task::getPoints这种方式调用每个task实例的getPoints方法
         * 把Task的stream转化为Integer的stream。
         * 最后，用sum函数把所有的分数加起来，得到最终的结果。
         */

        /**
         * 在继续讲解下面的例子之前，关于stream有一些需要注意的地方.
         * stream操作被分成了中间操作与最终操作这两种。
         */
        /**
         * 中间操作返回一个新的stream对象。中间操作总是采用惰性求值方式，
         * 运行一个像filter这样的中间操作实际上没有进行任何过滤，
         * 相反它在遍历元素时会产生了一个新的stream对象，
         * 这个新的stream对象包含原始stream中符合给定谓词的所有元素。
         */
        /**
         * 像forEach、sum这样的最终操作可能直接遍历stream，产生一个结果或副作用。
         * 当最终操作执行结束之后，stream管道被认为已经被消耗了，没有可能再被使用了。
         * 在大多数情况下，最终操作都是采用及早求值方式，及早完成底层数据源的遍历。
         */

        /**
         * stream另一个有价值的地方是能够原生支持并行处理。让我们来看看这个算task分数和的例子。
         * 这个例子和第一个例子很相似，
         *      但这个例子的不同之处在于这个程序是并行运行的，其次使用reduce方法来算最终的结果。
         */
        final double totalPoints = tasks
                .stream()
                .parallel() // 并行方法
                .map(task -> task.getPoints()) // 或 map( Task::getPoints )
                .reduce(0, Integer::sum);
        System.out.println("task分数： " + totalPoints);

        /**
         * 经常会有这个一个需求：
         *  我们需要按照某种准则来对集合中的元素进行分组。
         *  Stream也可以处理这样的需求，下面是一个例子：
         */
        final Map<Status, List<Task>> map = tasks
                .stream()
                .collect(Collectors.groupingBy(Task::getStatus));
        System.out.println(map);

        /**
         * 计算整个集合中每个task分数（或权重）的占比。
         */
        final Collection<String> result = tasks
                .stream()   // String<String>
                .mapToInt(Task::getPoints) // 将task的points转换成IntStream
                .asLongStream() // 将IntStream转换成LongStream
                .mapToDouble(points -> points / totalPoints) // 求出每个节点数与总节点数的比值并转换成DoubleStream
                .boxed()    // 转换成Stream<Double>    这一步 不写也不报错
                .mapToLong(weight -> (long)(weight * 100))  // 小数乘以100 转换成LongStream
                .mapToObj(percent -> percent + "%") // 转换成String格式的流 String<String>
                .collect(Collectors.toList());  // 将流转换成列表List<String>
        System.out.println(result);
        System.out.println(tasks.stream().collect(Collectors.toList()));

    }
}
